home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / mail / pine / ccmd / cmkey.c < prev    next >
Encoding:
C/C++ Source or Header  |  1991-10-12  |  13.3 KB  |  404 lines

  1. /*
  2.  Author: Andrew Lowry
  3.  
  4.  Columbia University Center for Computing Activities, July 1986.
  5.  Copyright (C) 1986, 1987, Trustees of Columbia University in the City
  6.  of New York.  Permission is granted to any individual or institution
  7.  to use, copy, or redistribute this software so long as it is not sold
  8.  for profit, provided this copyright notice is retained.
  9. */
  10. /* cmkey
  11. **
  12. ** Code to parse keywords.  Parsing succeeds if current input contains
  13. ** a delimiter, and the input up to that delimiter is nonempty and
  14. ** uniquely matches a non-ignored keyword in the supplied keyword
  15. ** table.  Completion succeeds in the same circumstances, except that
  16. ** no delimiter will be present, and returns the remainder of the
  17. ** matched keyword.  Standard help prints a list of all visible keywords
  18. ** that might match the current input.  The break table specifies which
  19. ** characters are allowed in keywords.  The standard table allows letters,
  20. ** digits, and hyphens in all positions.
  21. **/
  22.  
  23. #define    KEYERR                /* keyword error tbl allocated here */
  24.  
  25. #include "ccmdlib.h"            /* get standard symbols */
  26. #include "cmfncs.h"        /* and internal symbols */
  27.  
  28. /* Forward declaration of handler routines */
  29.  
  30. int keyprs(), keyhlp(), keycplt();
  31.         
  32. static brktab keybrk = {                /* standard break table */
  33.   {                    /* 1st char break array */
  34.                     /* all but letters, digits, hyphen */
  35.     0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0x00, 0x3f, 
  36.     0x80, 0x00, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x1f
  37.   },
  38.   {                    /* subsequent char break array */
  39.                     /* same as above */
  40.     0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0x00, 0x3f, 
  41.     0x80, 0x00, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x1f
  42.   }
  43. };
  44.  
  45. ftspec ft_key = { keyprs, keyhlp, keycplt, 0, &keybrk }; /* handler structure */
  46.  
  47.  
  48.  
  49. /* keyprs - Succeed if the current input is terminated and matches
  50. ** a single keyword (or exactly matches a non-ignored keyword).
  51. **/
  52.  
  53. PASSEDSTATIC int
  54. keyprs(text,textlen,fdbp,parselen,value)
  55. char *text;
  56. int textlen,*parselen;
  57. fdb *fdbp;
  58. pval *value;
  59. {
  60.   int mcount;                /* number of matches */
  61.   keywrd *mat;                /* the matching keyword */
  62.   char *term;                /* char terminating keyword */
  63.   keytab *kt;                /* the keyword table */
  64.  
  65.   mcount = match(text,textlen,fdbp,&mat,&term);    /* find matching keys */
  66.   if (mcount == 0)
  67.     return(KEYxNM);            /* no match */
  68.   else if (term == NULL)        /* not terminated? */
  69.     return(CMxINC);            /* then incomplete parse */
  70.   else if (mcount > 1)
  71.     return(KEYxAMB);            /* ambiguous */
  72.  
  73.   kt = (keytab *) fdbp->_cmdat;        /* unique - get table address */
  74.   while ((mat)->_kwflg & KEY_ABR)    /* track down abbreviations */
  75.     if (((mat)->_kwval >= kt->_ktcnt) || /* abbrev ptr out of bounds? */
  76.     ((mat)->_kwval < 0)
  77.        )
  78.       return(KEYxABR);            /* bad abbrev chain */
  79.     else
  80.       mat = &(kt->_ktwds[mat->_kwval]);    /* move down chain */
  81.  
  82.   *parselen = term - text;        /* compute field length */
  83.  
  84.   if (fdbp->_cmffl & KEY_EMO) {        /* exact match only? */
  85.       if (*parselen != strlen (mat->_kwkwd))
  86.       return KEYxNM;        /* it's not a match */
  87.   }
  88.   value->_pvkey = mat->_kwval;        /* copy value from keyword */
  89.   return(CMxOK);            /* and give a good parse */
  90. }
  91.  
  92.  
  93.  
  94. /* keycplt - If the current input is ambiguous, we beep.  Otherwise
  95. ** we complete with the remainder of the identified keyword.  In the
  96. ** latter case, full completion adds a space and a wakeup, while
  97. ** partial completion stops after punctuation.
  98. **/
  99.  
  100. PASSEDSTATIC int
  101. keycplt(text,textlen,fdbp,full,cplt,cpltlen)
  102. char *text,**cplt;
  103. int textlen,full,*cpltlen;
  104. fdb *fdbp;
  105. {
  106.   int mcount;                /* number of matching keywords */
  107.   keywrd *mat;                /* unique matching key */
  108.   char *term;                /* char terminating the keyword */
  109.   keytab *kt;                /* the keyword table */
  110.  
  111.   *cplt = NULL;                /* assume empty completion */
  112.   mcount = match(text,textlen,fdbp,&mat,&term);    /* find matching keys */
  113.   if (mcount > 1) {
  114.     char *partial();
  115.     *cplt = partial(text,textlen, fdbp->_cmdat,mcount);
  116.     *cpltlen = strlen(*cplt);
  117.     return(CMP_BEL);            /* beep if ambiguous */
  118.   }
  119.   else {
  120.     kt = (keytab *) fdbp->_cmdat;    /* unique - get table address */
  121.     while ((mat)->_kwflg & KEY_ABR)    /* track down abbreviations */
  122.       mat = &(kt->_ktwds[mat->_kwval]);    /* move down chain */
  123.  
  124.     *cplt = mat->_kwkwd+textlen;    /* point at completion text */
  125.     *cpltlen = strlen(mat->_kwkwd) - textlen; /* compute completion len */
  126.     if (*cpltlen < 0)            /* can happen on weird abbrevs */
  127.       *cplt = NULL;            /* no completion in that case */
  128.     if (full)                /* full completion? */
  129.       return(CMP_SPC | CMP_GO);     /* succeed with space and wakeup */
  130.     else
  131.       return(CMP_PNC);            /* partial stops after punctuation */
  132.   }
  133. }
  134.  
  135. /* keyhlp - Construct a set of matching keywords for the current input,
  136. ** then print them in tabular form.  Do not print invisible keywords.
  137. ** If no keywords match, print a special indication.  The help message
  138. ** starts with "keyword, one of the following:".  If custom help has
  139. ** been given, "keyword, " is left off of this string.
  140. **/
  141.  
  142. PASSEDSTATIC int
  143. keyhlp(text,textlen,fdbp,cust)
  144. char *text;
  145. int textlen,cust;
  146. fdb *fdbp;
  147. {
  148.   int mcount;                /* number of matches */
  149.   keywrd *mat;                /* a matching keyword */
  150.   char *term;                /* character terminating input */
  151.   keytab *kt;                /* keyword table */
  152.   int i,j;
  153.   int keylen;                /* lengths of individual keywords */
  154.   int cols;                /* number of words printed per line */
  155.   int curcol;                /* current table column */
  156.   int maxlen = 0;            /* maximum keyword length */
  157.  
  158.   if (!cust)
  159.     cmxputs("keyword, ");        /* start of msg with no custom help */
  160.   cmxputs("one of the following:");    /* remainder of the first line */
  161.       
  162.   mcount = match(text,textlen,fdbp,&mat,&term);    /* find matching keys */
  163.   if (mcount == 0) {            /* no match */
  164.     cmxputs(" (No keywords match current input)"); /* indicate void */
  165.     return(CMxOK);
  166.   }
  167.   kt = (keytab *) fdbp->_cmdat;        /* get keywrd table from FDB */
  168.   mat = kt->_ktwds;            /* point to first keyword */
  169.   for (i = 0; i < kt->_ktcnt; i++) {    /* first pass to find longest kwd */
  170.     if ((mat->_kwflg & KEY_MAT) &&     /* matching keyword... */
  171.         ((mat->_kwflg & KEY_INV) == 0)    /* and not invisible */
  172.        ) {
  173.       keylen = strlen(mat->_kwkwd);    /* get keyword length */
  174.       if (keylen > maxlen)
  175.     maxlen = keylen;        /* update longest size */
  176.     }
  177.     mat++;                /* and move to next keyword */
  178.   }
  179.   maxlen += 3;                /* adjust for column separation */
  180.  
  181.   /* Following calculation goes as follows:
  182.   ** Let w be the screen width.  Naively, we would calculate that
  183.   ** we could list floor(w/maxlen) keywords per line, each taking up
  184.   ** maxlen columns on the screen.  But since each line is indented
  185.   ** two spaces, we should subtract 2 from the effective screen width.
  186.   ** Then we add 3 to the effective width because we will not need to
  187.   ** print three spaces after the last keyword in a line.  So the
  188.   ** effective screen width is w-2+3 = w+1 = cmcsb._cmcmx+2, since
  189.   ** cmcsb._cmcmx is one less than the screen width.  So we want
  190.   ** floor((cmcsb._cmcmx+2)/maxlen) keywords per line.
  191.   **/
  192.  
  193.   /*
  194.    * The reasoning above ignores the problem with automargins on non-"xn"
  195.    * terminals.  On an 80-column terminal with three columns of 24-char
  196.    * keywords, for instance, the terminal will wrap around to the next
  197.    * line and the cmxnl() below would be inappropriate.  So until someone
  198.    * cleans up the rest of the ccmd output routines, we have to avoid the
  199.    * rightmost column.
  200.    *
  201.    * MM happens to have 24-character keywords which would look kind of silly
  202.    * displayed in only two columns, so we compensate by subtracting one of
  203.    * the spaces displayed at the beginning of the line.  TOPS-20 only prints
  204.    * one space at the beginning of the line, so I don't feel too guilty
  205.    * about this... -- chris
  206.    */
  207.  
  208.   cols = (cmcsb._cmcmx+2) / maxlen;    /* number of columns per line */
  209.   if (cols <= 0) cols = 1;        /* minimum we will allow */
  210.   mat = kt->_ktwds;            /* point to first keyword */
  211.   curcol = 0;                /* currently printing first column */
  212.   for (i = 0; i < kt->_ktcnt; i++) {    /* second pass to print matches */
  213.     if ((mat->_kwflg & KEY_MAT) &&    /* matching keyword? */
  214.     ((mat->_kwflg & KEY_INV) == 0)    /* and visible? */
  215.        ) {
  216.       if (curcol == 0) {
  217.     cmxnl();            /* new line for first column */
  218.     cmxputs(" ");            /* and offset a bit */
  219.       }
  220.       cmxputs(mat->_kwkwd);        /* print the keyword */
  221.       if (curcol < (cols-1))        /* space out if not last column */
  222.     for (j = strlen(mat->_kwkwd); j < maxlen; j++)
  223.        cmxputc(SPACE);
  224.       curcol = (curcol+1) % cols;    /* and move to next column */
  225.     }
  226.     mat++;                /* move to next keyword */
  227.   }
  228.   return(CMxOK);            /* all done */
  229. }
  230.  
  231.  
  232.  
  233. /* match - Auxiliary routine used by keyword handlers.
  234. **
  235. ** Purpose:
  236. **   Step through all the keywords in a table, and set their KEY_MAT
  237. **   flags according to whether or not they match a given input string.
  238. **   Returns the number of matching keywords, and if only one keyword
  239. **   matches, a pointer to that keyword.  If more than one keyword
  240. **   matches, but one keyword matches exactly, it is returned as if
  241. **   it were the only matching keyword.  KEY_INV, KEY_NOR and KEY_ABR
  242. **   flags do not affect this operation, except that an exact match
  243. **   to a KEY_NOR flagged keyword will not be considered exact.
  244. **
  245. ** Input arguments:
  246. **   text - A pointer to the first input character.
  247. **   textlen - The number of input characters.
  248. **   fdbp - A pointer to the FDB pointing to the keyword table
  249. **     and break table to be used.
  250. ** 
  251. ** Output arguments:
  252. **   mat - A pointer to the matching keyword, if exactly one keyword
  253. **     matched or if one of the matching keywords was an exact match.
  254. **   term - A pointer to the first character following the input that
  255. **     was used to match keywords, if any characters were left after
  256. **     that input.  Otherwise, NULL is returned here.
  257. **
  258. ** Returns: The number of matching keywords (or 1 for an exact match).
  259. **/
  260.  
  261. static int
  262. match(text,textlen,fdbp,mat,term)
  263. char *text,**term;
  264. int textlen;
  265. fdb *fdbp;
  266. keywrd **mat;
  267. {
  268.   int mcount = 0;            /* number of matches seen */
  269.   int inlen;                /* # of chars to match in input */
  270.   int i;
  271.   int exact = FALSE;            /* true if exact match occurs */
  272.   keywrd *kwds;                /* for stepping through table */
  273.   brktab *btab;                /* break table to use */
  274.   keytab *kt;                /* keyword table to search */
  275.   
  276.   if ((btab = fdbp->_cmbrk) == NULL)    /* get supplied break table */
  277.     btab = &keybrk;            /* or use default */
  278.  
  279.   for (inlen = 0; inlen < textlen; inlen++) /* find # of usable chars */
  280.     if (BREAK(btab,text[inlen],inlen))    /* stop on first break char */
  281.       break;
  282.   if (inlen == textlen)            /* no break char? */
  283.     *term = NULL;            /* then set no terminator */
  284.   else
  285.     *term = text+inlen;            /* else point to it for caller */
  286.   
  287.   kt = (keytab *) fdbp->_cmdat;        /* point to keyword table */
  288.   if (inlen == 0 && textlen != 0)
  289.       return(0);
  290.   kwds = kt->_ktwds;            /* point to first keyword */
  291.   for (i = 0; i < kt->_ktcnt; i++) {    /* step through table */
  292.     if (match1(kwds->_kwkwd,text,inlen)) {
  293.       kwds->_kwflg |= KEY_MAT;        /* this keyword matches */
  294.       if (!exact) {            /* if no prior exact match */
  295.     *mat = kwds;            /* then save pointer to return */
  296.         mcount++;            /* and count it */
  297.       }
  298.       if (strlen(kwds->_kwkwd) == inlen) /* exact match? */
  299.         if ((kwds->_kwflg & KEY_NOR) == 0) { /* and not ignored? */
  300.       exact = TRUE;            /* flag this exact match */
  301.           mcount = 1;            /* only one match now */
  302.         }
  303.     }
  304.     else
  305.       kwds->_kwflg &= ~KEY_MAT;        /* no match -- turn off flag */
  306.  
  307.     kwds++;                /* move on to next keyword */
  308.   }
  309.   kwds = kt->_ktwds;
  310.   if (mcount == 1)
  311.     for (i = 0; i < kt->_ktcnt; i++,kwds++) {    /* step through table */
  312.       if (kwds->_kwflg & KEY_MAT && kwds->_kwflg & KEY_NOR) {
  313.     kwds->_kwflg &= ~KEY_MAT;
  314.     mcount = 0;
  315.     *mat = NULL;
  316.     break;
  317.       }
  318.     }
  319.   return(mcount);            /* give back # of matches */
  320. }
  321.  
  322.  
  323.  
  324. /* match1 - Auxiliary routine for match 
  325. **
  326. ** Purpose:
  327. **   Decides whether or not two strings match up to a given number of
  328. **   characters.  Case of letters is ignored in the comparison.
  329. **
  330. ** Input arguments:
  331. **   s1, s2 - Pointers to the strings to be compared.
  332. **   slen - Number of characters to compare.
  333. **
  334. ** Output arguments: None.
  335. ** Returns: TRUE for a match, FALSE otherwise.
  336. **/
  337.  
  338. static int
  339. match1(s1,s2,slen)
  340. char *s1,*s2;
  341. int slen;
  342. {
  343.   char c1,c2;            /* individual chars to compare */
  344.   while (slen-- > 0) {        /* step through strings */
  345.     c1 = (*s1++) & CC_CHR;        /* pick up next pair of chars */
  346.     c2 = (*s2++) & CC_CHR;
  347.     if (islower(c1))
  348.       c1 = toupper(c1);        /* upper case first char */
  349.     if (islower(c2))
  350.       c2 = toupper(c2);        /* upper case second char */
  351.     if (c1 != c2)
  352.       return(FALSE);        /* mismatch */
  353.   }
  354.   return(TRUE);            /* all chars matched */
  355. }
  356.  
  357. PASSEDSTATIC char *
  358. partial(text,textlen,kt,pcount) 
  359. char *text; 
  360. int textlen;
  361. keytab *kt;
  362. int pcount;
  363. {
  364.     int i;
  365.     static char buf[50];
  366.     keywrd *k;
  367.     int first = TRUE;
  368.     int len;
  369.  
  370.     buf[0] = '\0';
  371.     for(i = 0; i < kt->_ktcnt; i++) {
  372.     k = &kt->_ktwds[i];
  373.     if (!(k->_kwflg & KEY_MAT))
  374.         continue;
  375.     if (k->_kwflg & KEY_NOR)
  376.         continue;
  377.     if (first) {
  378.         strcpy(buf,k->_kwkwd);
  379.         first = FALSE;
  380.     }
  381.     else  {
  382.         len = matchlen(buf,k->_kwkwd);
  383.         buf[len] = '\0';
  384.         if (len == textlen)
  385.         break;
  386.     }
  387.     }
  388.     return(&buf[textlen]);
  389. }
  390.  
  391. #ifdef toupper
  392. #undef toupper
  393. #endif
  394. #define toupper(c) (islower(c) ? (c)-'a'+'A' : (c))
  395. matchlen(s1,s2)
  396. register char *s1, *s2;
  397. {
  398.     register int i;
  399.  
  400.     for(i = 0; (toupper(*s1) == toupper(*s2)); s1++,s2++,i++);
  401.     return(i);
  402. }
  403.     
  404.